<?php

Loader::using("underscore::libs::Plexus::Plexus");
Loader::using("underscore::core::ModulesCollection");
Loader::using("underscore::core::exceptions::ModuleNotFoundException");

/**
 * Singleton Module Storage. Controls the modules execution.
 *
 * @author Virtualidad
 */
class ModuleStorage extends AbstractCompositionObject {

    /**
     *
     * @var ModulesCollection
     */
    private $modules;
    private $plugins = array();
    /**
     *
     * @var ModuleCall
     */
    private $currentCall;

    /**
     * Loads all necesary objects.
     */
    public function load() {
        $this->findLoaders();
        $this->findPlugins();

        $this->satisfyImports();
    }

    /**
     * Tells if given application exists.
     * 
     * @param string $app The application name.
     * @return boolean If whether or not the application exists. 
     */
    public function applicationExsists($app) {
        return $this->modules->applicationExists($app);
    }

    /**
     * Executes a module.
     * 
     * @param ModuleCall $call An object that specifies exactly which module will be executed.
     * 
     * @return string The output resulting from the module execution. 
     * 
     * @see ModuleCall
     * 
     * @throws UnderscoreException If any exception is thrown during module
     * execution, this is wrapped and re-thrown with the information about the
     * module being executed.
     */
    public function execute(ModuleCall $call) {
        
        $this->currentCall = $call;
        
        try {
            $this->modules->loadApp($call->getApp());
            
            $selectedModule = $this->resolveModule($call->getModule());
            if ($call->hasUnhandledException()) {
                $selectedModule->setLastUnhandledException($call->getLastUnhandledException());
            }

            $plexus = new Plexus(new PluginCollection($this->plugins));
            $result = $plexus->doExecute($selectedModule, $call->getTask());
        } catch (Exception $ex) {
            $wrapped = $this->wrapException($ex);
            throw $wrapped;
        }

        return $result;
    }

    private function wrapException($ex) {
        /* @var $lastUnhandledException UnderscoreException */
        $lastUnhandledException;

        if ($ex instanceof UnderscoreException) {
            $lastUnhandledException = $ex;
        } else {
            $lastUnhandledException = new UnderscoreException(
                    $this->currentCall->getApp(), 
                    $this->currentCall->getModule(), 
                    $this->currentCall->getTask(),
                    $ex);
        }

        return $lastUnhandledException;
    }

    /**
     *
     * @return ModulesCollection 
     */
    public function getModules() {
        return $this->modules;
    }

    /**
     *
     * @import-many Plugins
     */
    public function setPlugins(array $plugins = array()) {
        $this->plugins = $plugins;
    }

    /**
     * @import ModuleContext
     */
    public function setModulesContext(ModulesContext $modulesContext) {
        ModulesContext::resolveInstance($modulesContext);
    }

    private function __construct() {
        $this->modules = new ModulesCollection();
        $this->load();
    }

    private static $instance;

    /**
     * Gets the only ModuleStorage instance.
     * 
     * @return ModuleStorage The ModuleStorage instance.
     */
    public static function instance() {
        if (!isset(self::$instance)) {
            self::$instance = new ModuleStorage();
        }

        return self::$instance;
    }

    /**
     *
     * @param string $module
     * @return Module
     */
    private function resolveModule($module) {
        $selectedModuleIndex = "$module";

        if ($this->modules->moduleIsLoaded($selectedModuleIndex)) {
            $selectedModule = $this->modules->get($selectedModuleIndex);
        } else if ($this->modules->defaultModuleIsLoaded()) {
            $selectedModule = $this->modules->getDefaultModule();
        } else {
            throw new ModuleNotFoundException($module, $this->modules->getCurrentApp());
        }

        return $selectedModule;
    }

    private function findPlugins() {
        Loader::using("ext::plugins::*");
    }

    /**
     * Finds and includes all Loaders.
     */
    private function findLoaders() {
        Loader::using("ext::loaders::*");
    }

    /**
     *
     *{@inheritdoc}
     */
    protected function getContainer() {
        return new MEF_Container_Default();
    }
}

?>
